"""
Standard environment implementation.  Reads a specified .env file to see the environment.
Environment variables are created by generators, as specified in the .env file.

The interface for environmet generators is:

    my_generator.generate(environment_key,request) # returns the environment value

Environment Files
-----------------
Environment files must end in a ".env" suffix, and be laid out as follows:

----

from epijam.middleware.environment.standard import StringGenerator
from epijam.zocalo.environment import MemberGenerator

entries = {
    'section':StringGenerator('home'),
    'application':StringGenerator('zocalo'),
    'member':MemberGenerator()
}

----

Note that there must be a dictionary named "entries" defined with string keys and
generators as values.  The one exception to this is - StringGenerators are assumed if
a string literal is placed in the value.  Therefore the above file could actually be
written like this:

----
from epijam.zocalo.environment import MemberGenerator

entries = {
    'section':'home',
    'application':'zocalo',
    'member':MemberGenerator()
}


----
"""

from django.conf import settings
import os
import os.path as path
from types import StringType
import logging

log = logging.getLogger('environment.standard')

# ==============
# = Generators =
# ==============

class StringGenerator(object):
    """
    Returns the string it was initialized with.
    """
    def __init__(self,string):
        self.string = string
    
    def generate(self,key,request):
        return self.string


class StringDictGenerator(object):
    """
    Returns a mapping.
    """
    def __init__(self,**kwargs):
        self.mappings = kwargs
    
    def generate(self,key,request):
        return self.mappings[key]

class URIGenerator(object):
    """
    Returns the URI of the request.
    """
    def generate(self,key,request):
        path = request.META['PATH_INFO']
        try:
            if request.META['QUERY_STRING']:
                path += '?'+request.META['QUERY_STRING']
        except KeyError:
            pass
        return path

class FunctionGenerator(object):
    """
    Executes a no-arg function.  Nice to combine with lambdas.
    """
    def __init__(self,func):
        self.func = func
    
    def generate(self,key,request):
        return self.func()

class ConditionalAttributeGenerator(object):
    """
    Returns value of attribute if it exists and has a true value,
    otherwise the alternate value or None is returned.
    """
    def __init__(self,target,attribute,alternate=None):
        self.target = target
        self.attribute = attribute
        self.alternate = alternate
    
    def generate(self,key,request):
        if hasattr(self.target,self.attribute):
            return getattr(self.target,self.attribute)
        else:
            return self.alternate

class CookieGenerator(object):
    """
    Returns the value of the named cookie, or None if the cookie
    is undefined.
    """
    def __init__(self,cookie_name=None):
        """
        If cookie name is not defined the name of the key will be used as the
        cookie name.
        """
        self.cookie_name = cookie_name
    
    def generate(self,key,request):
        try:
            if self.cookie_name:
                return request.COOKIES[self.cookie_name]
            else:
                return request.COOKIES[key]
        except KeyError:
            return None

class RequestAttributeGenerator(object):
    """
    Returns the attribute value from the request.
    """
    def __init__(self,attribute_name=None):
        """
        If the attribute name is not specified then the key will be used
        as the attribute name.
        """
        self.attribute_name = attribute_name
    
    def generate(self,key,request):
        try:
            if self.attribute_name:
                return getattr(request,self.attribute_name)
            else:
                return getattr(request,key)
        except AttributeError:
            return None

class AuthProfileGenerator(object):
    """
    Gets the auth profile from the request.
    """
    def generate(self,key,request):
        """
        Retrieves the profile from the request user.
        """
        if request.user.is_anonymous():
            return None
        else:
            try:
                return request.user.get_profile()
            except AttributeError:
                log.exception('Exception while getting profile from request user.')
                return None

class RequestParameterGenerator(object):
    """
    Takes the data from POST, if present or
    otherwise GET and assigns it to the
    environmsne.
    """
    def generate(self,key,request):
        return request.POST or request.GET

# =======================================
# = Loading environment from .env files =
# =======================================

def process_env_file(filepath):
    """
    Reads the file and returns a dictionary of keys and value generators.
    """
    env_file = open(filepath)
    exec env_file.read() # magically creates entries
    env_file.close()
    
    # auto-build string generators + sanity check
    string_generator_update = {}
    for key, value in entries.iteritems():
        if isinstance(value,StringType):
            string_generator_update[key] = StringGenerator(value)
        elif not hasattr(value,'generate'):
            raise Exception, 'Environment value generator %s has no generate method!' % str(value)
    
    if string_generator_update:
        entries.update(string_generator_update)
    
    return entries

class EnvironmentLoader(object):
    """
    Loads the enviroment from the ENVPATH.
    """
    def __init__(self):
        """
        Constructor.
        """
        self.generators = {}
        
        try:
            envpath = settings.ENVPATH
        except AttributeError:
            try:
                envpath = os.environ['ENVPATH']
            except KeyError:
                raise Exception, 'Must define ENVPATH to use the standard environment.'
        
        envpath_dirs = envpath.split(':') #TODO - replace this with something platform neutral!
        for envpath_dir in envpath_dirs:
            for dirpath, dirnames, filenames in os.walk(envpath_dir):
                for filename in filenames:
                    if filename.endswith('.env'):
                        self.generators.update(process_env_file(path.join(dirpath,filename)))
    
    def __call__(self,request):
        """
        Function hook.
        """
        env = {}
        for key in self.generators.keys():
            env[key] = self.generators[key].generate(key,request)
        return env


# =======================
# = Hook for middleware =
# =======================
get_env = EnvironmentLoader()